package cn.lizemin.controller;

import cn.lizemin.service.OrderService;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

/**
 * @author lzm
 * @version 1.0
 * @description: 秒杀
 * @date 2022/3/6 18:28
 */
@RestController
@RequestMapping("stock")
@Slf4j
public class StockController {

    @Autowired
    private OrderService orderService;

    //创建令牌桶实例
    private RateLimiter rateLimiter = RateLimiter.create(10);

    /**
     * 在controller层接口中，解决线程安全问题，而不是在service层中
     * 开发秒杀方法
     * 步骤：
     *
     * @param id
     * @return
     */
//    @GetMapping("kill")
//    public String kill(Integer id) {
//        System.out.println("秒杀商品的id = " + id);
//        try {
//            synchronized (this) {
//                //根据秒杀商品id 去调用秒杀业务
//                int orderId = orderService.kill(id);
//                return "秒杀成功,订单id为: " + String.valueOf(orderId);
//            }
//
//        } catch (Exception e) {
//            e.printStackTrace();
//            return e.getMessage();
//        }
//    }

    /**
     * 使用乐观锁解决线程安全导致的超卖问题
     * @param id
     * @return
     */
    @GetMapping("kill")
    public String kill(Integer id) {
        System.out.println("秒杀商品的id = " + id);
        try {
            //根据秒杀商品id 去调用秒杀业务
            int orderId = orderService.kill(id);
            return "秒杀成功,订单id为: " + String.valueOf(orderId);
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

    /**
     * 使用guava的令牌桶算法限流对系统提供保护（丢掉或者阻塞超额的请求）
     *      每次只创建固定数量的token令牌，如果并发量在每次创建的令牌数量范围之内，则无需等待令牌。
     *      如果超过每次生成的令牌量，部分请求将会等待新创建的令牌来获取执行权限
     * @param id
     * @return
     */
    @GetMapping("sale")
    public String sale(Integer id){
        //1.没有获取到token请求则一直等待（阻塞），直到获取到token 令牌
        log.info("等待的时间: "+  rateLimiter.acquire());

        //2.设置一个等待时间,如果在等待的时间内获取到了token 令牌,则处理业务,
        // 如果在等待时间内没有获取到响应token则抛弃请求，而漏桶策略则是一律抛弃
        if(!rateLimiter.tryAcquire(2, TimeUnit.SECONDS)){
            System.out.println("当前请求被限流,直接抛弃,无法调用后续秒杀逻辑....");
            return "抢购失败!";
        }
        System.out.println("处理业务.....................");
        return "抢购成功";
    }

    /**
     * 开发一个秒杀方法 乐观锁防止超卖+ 令牌桶算法限流
     *  在乐观锁的基础上，添加请求限流
     * @param id
     * @return
     */
    @GetMapping("killtoken")
    public String killtoken(Integer id) {
        System.out.println("秒杀商品的id = " + id);
        //加入令牌桶的限流措施
        if (!rateLimiter.tryAcquire(2, TimeUnit.SECONDS)) {
            log.info("抛弃请求: 抢购失败,当前秒杀活动过于火爆,请重试");
            return "抢购失败,当前秒杀活动过于火爆,请重试!";
        }
        try {
            //根据秒杀商品id 去调用秒杀业务
            int orderId = orderService.kill(id);
            return "秒杀成功,订单id为: " + String.valueOf(orderId);
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }


    //生成md5值的方法
    @RequestMapping("md5")
    public String getMd5(Integer id, Integer userid) {
        String md5;
        try {
            md5 = orderService.getMd5(id, userid);
        }catch (Exception e){
            e.printStackTrace();
            return "获取md5失败: "+e.getMessage();
        }
        return "获取md5信息为: "+md5;
    }


    /**
     * 开发一个秒杀方法 乐观锁防止超卖+ 令牌桶算法限流 + md5签名(做接口隐藏)
     *          接口隐藏就是根据用户的每次请求携带的用户id来生成一个md5 token,
     *              而且这个token是有生存时间的。
     *              如果用户写了一个脚本来参与秒杀，那这个脚本也只能在token有效期内工作，
     *              token过期后，需要重新根据md5接口生成token
     * @param id
     * @param userid
     * @param md5
     * @return
     */
    @GetMapping("killtokenmd5")
    public String killtoken(Integer id,Integer userid,String md5) {
        System.out.println("秒杀商品的id = " + id);
        //加入令牌桶的限流措施
        if (!rateLimiter.tryAcquire(3, TimeUnit.SECONDS)) {
            log.info("抛弃请求: 抢购失败,当前秒杀活动过于火爆,请重试");
            return "抢购失败,当前秒杀活动过于火爆,请重试!";
        }
        try {
            //根据秒杀商品id 去调用秒杀业务
            int orderId = orderService.kill(id,userid,md5);
            return "秒杀成功,订单id为: " + String.valueOf(orderId);
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }



}
